package dataflowscheme.engine.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;

/**
 * Storage handler to store and provide instances of
 * Storages' referenced classes and locks, to avoid 
 * parallel execution of the same class.
 * 
 * This class is singleton. 
 * 
 * @author <a href="mailto:misiak7@gmail.com">Michal Antolik</a>
 *
 */
public class StorageSourcesHandler {

	static private StorageSourcesHandler instance = null;
	
	static public StorageSourcesHandler getInstance(){
		if(instance == null){
			instance = new StorageSourcesHandler();
		}
		
		return instance;
	}
	
	private StorageSourcesHandler(){
		
	}
	
	/** Map of source instance for particular class */
	private Map<Class<?>, Object> sourceInstances = new HashMap<Class<?>, Object>();
	
	/** Map of locks for particular class */
	private Map<Class<?>, Lock> sourcesLocks = new HashMap<Class<?>,Lock>();
	
	static private Logger log = Logger.getLogger(StorageSourcesHandler.class);
	
	protected static Class<?>[] zeroParams = new Class<?>[0];
	
	/**
	 * Provide an instance of referenced class. If it does not
	 * exist, new one is created.
	 * 
	 * @param sourceClass class referenced by a Storage 
	 * @return an instance of referenced class
	 * 
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	public Object getSourceInstance(Class<?> sourceClass) throws InstantiationException, IllegalAccessException{
		if(sourceInstances.containsKey(sourceClass)){
			return sourceInstances.get(sourceClass);
		}
		
		Object si = sourceClass.newInstance();
		
		try{
			//invoke start phase
			Method m = sourceClass.getMethod("start", zeroParams);
			m.invoke(si, new Object[0]);
		}catch(NoSuchMethodException nsme){
			log.error("Error while initializing source '" + sourceClass + "'", nsme);
		}catch(SecurityException se){
			log.error("Error while initializing source '" + sourceClass + "'", se);
		}catch(IllegalAccessException iae){
			log.error("Error while initializing source '" + sourceClass + "'", iae);
		}catch(IllegalArgumentException iae){
			log.error("Error while initializing source '" + sourceClass + "'", iae);
		}catch(InvocationTargetException ite){
			log.error("Error while initializing source '" + sourceClass + "'", ite);
		}catch(Exception e){
			log.error("Unknown error while initializing source '" + sourceClass + "'", e);
		}
		
		//cache new instance and send back
		sourceInstances.put(sourceClass, si);
		return si;
	}
	
	/**
	 * Call <code>stop()</code> method on each instance of Storage's referenced class 
	 */
	public void finalizeStorageInstances(){
		for(Map.Entry<Class<?>, Object> entry: sourceInstances.entrySet()){
			try{
				//invoke stop phase
				log.debug("Finalizing source '" + entry.getKey() + "'");
				Method m = entry.getKey().getMethod("stop", zeroParams);
				m.invoke(entry.getValue(), new Object[0]);
			}catch(NoSuchMethodException nsme){
				log.error("Error while finalizing source '" + entry.getKey() + "'", nsme);
			}catch(SecurityException se){
				log.error("Error while finalizing source '" + entry.getKey() + "'", se);
			}catch(IllegalAccessException iae){
				log.error("Error while finalizing source '" + entry.getKey() + "'", iae);
			}catch(IllegalArgumentException iae){
				log.error("Error while finalizing source '" + entry.getKey() + "'", iae);
			}catch(InvocationTargetException ite){
				log.error("Error while finalizing source '" + entry.getKey() + "'", ite);
			}catch(Exception e){
				log.error("Unknown error while finalizing source '" + entry.getKey() + "'", e);
			}
		}
	}
	
	/**
	 * Provide a Lock instance of referenced class. If it does not
	 * exist, new one is created.
	 * 
	 * @param sourceClass class referenced by a Storage 
	 * @return Lock unique for a referenced class
	 */
	public Lock getSourceLock(Class<?> sourceClass){
		if(sourcesLocks.containsKey(sourceClass)){
			return sourcesLocks.get(sourceClass);
		}
		
		Lock lock = new ReentrantLock(true);
		sourcesLocks.put(sourceClass, lock);
		
		return lock;
	}
}
